﻿namespace IntegrationTests.Samples
{
  using System;
  using System.IO;
  using System.Linq;
  using System.Reflection;
  using System.Threading;
  using System.Xml.Linq;
  using Microsoft.VisualStudio.TestTools.UnitTesting;

  /// <summary>
  /// Runs a test of FibonacciSquared orchestration in
  /// the Contoso.Samples BizTalk application. The generation of
  /// the Fibonacci sequece is handeled by an external service.
  /// For testing the external service is emulated by this test.
  /// </summary>
  [TestClass]
  public class FibonacciSquaredExternal
  {
    /// <summary>
    /// The file share where request messages are sent to.
    /// </summary>
    private const string SendToFolder = @"\\btap1\TestData\Samples\In";

    /// <summary>
    /// The file share where response messages are read from.
    /// </summary>
    private const string ReceiveFromFolder = @"\\btap1\TestData\Samples\Out";

    /// <summary>
    /// The file share where request to the Fibonacci service are read from.
    /// </summary>
    private const string GenerateFibonacciRequestFolder = @"\\btap1\TestData\GenerateFibonacci\Request";
    
    /// <summary>
    /// The file share where responses from the Fibonacci service are sent to.
    /// </summary>
    private const string GenerateFibonacciResponseFolder = @"\\btap1\TestData\GenerateFibonacci\Response";

    /// <summary>
    /// The worker thread checks this variable in order to know when to stop.  
    /// </summary>
    private volatile bool running = false;
       
    /// <summary>
    /// The entry point for the test.
    /// </summary>
    [TestMethod]
    [Timeout(30000)]
    public void FibonacciSquaredExternalTest()
    {
      string id;
      string responseFilename;
      XDocument request;
      XDocument expectedResponse;
      XDocument actualResponse;
      XNamespace ns1;
      XNamespace ns2;
      Thread worker;

      this.running = true;

      worker = new Thread(this.ProcessRequests);
      worker.Start();

      // Define the namespaces we need.
      ns1 = "http://contoso.com/biztalkdemo/fibonaccisquaredrequest/v1";
      ns2 = "http://contoso.com/biztalkdemo/fibonaccisquaredresponse/v1";

      // Load the docuemnt templates.
      request = XDocument.Load(this.GetResourceStream("FibonacciSquaredRequest.xml"));
      expectedResponse = XDocument.Load(this.GetResourceStream("FibonacciSquaredResponse.xml"));

      // Create a new unique ID
      id = Guid.NewGuid().ToString();

      // Replace the ID in our messages in order to
      // create a uniqe message for each test run.
      request.Descendants(ns1 + "UnitOfWorkID").First().Value = id;
      expectedResponse.Descendants(ns2 + "UnitOfWorkID").First().Value = id;

      // Send the request message to BizTalk, using the ID as the file name.
      request.Save(Path.Combine(SendToFolder, id + ".xml"));

      // The BizTalk application outputs the original filename,
      // so we know what to look for.
      responseFilename = Path.Combine(ReceiveFromFolder, id + ".xml");

      // Poll for the file. If BizTalk fails and never outputs the
      // file, the test will stop on time-out.
      while (!File.Exists(responseFilename))
      {
        Thread.Sleep(1000);
      }

      this.running = false;

      // Load the actual response.
      actualResponse = XDocument.Load(responseFilename);

      // Compare the actual and expected XML documents.
      // Note that the DeepEquals method is sensitive to syntax differences.
      // You may need to adjust the expected message for the comparison to work.
      Assert.IsTrue(XNode.DeepEquals(actualResponse, expectedResponse));

      // Cleanup the file
      File.Delete(responseFilename);
    }

    /// <summary>
    /// This method runs in a separate thread and act as the Fibonacci service.
    /// </summary>
    private void ProcessRequests()
    {
      XDocument request;
      XDocument response;
      XNamespace ns1;
      XNamespace ns2;
      XElement seq;
      string uow;
      string length;
      long[] f;

      ns1 = "http://contoso.com/biztalkdemo/generatefibonaccirequest/v1";
      ns2 = "http://contoso.com/biztalkdemo/generatefibonacciresponse/v1";

      while (this.running)
      {
        foreach (string file in Directory.EnumerateFiles(GenerateFibonacciRequestFolder, "*.xml"))
        {
          request = XDocument.Load(file);
          uow = request.Descendants(ns1 + "UnitOfWorkID").First().Value;
          length = request.Descendants(ns1 + "Length").First().Value;

          f = this.GenerateFibonacciSequence(int.Parse(length));

          seq = new XElement(ns2 + "FibonacciSequence");
          for (int i = 0; i < f.Length; i++)
          {
            seq.Add(new XElement(ns2 + "V", f[i]));
          }
          
          response = new XDocument(
                     new XElement(ns2 + "GenerateFibonacciResponse",
                      new XElement(ns2 + "UnitOfWorkID", uow),
                      seq));

          response.Save(Path.Combine(GenerateFibonacciResponseFolder, uow + ".xml"));
          File.Delete(file);
        }

        Thread.Sleep(1000);
      }
    }

    /// <summary>
    /// Generates the Fibonacci sequence.
    /// </summary>
    /// <param name="length">Number of Fibonacci numbers to generate.</param>
    /// <returns>The Fibonacci sequence.</returns>
    private long[] GenerateFibonacciSequence(int length)
    {
      long[] f;

      if (length < 1)
      {
        throw new ArgumentOutOfRangeException("Must be greater than 0", "length");
      }

      if (length > 93)
      {
        throw new ArgumentOutOfRangeException("Must be less than 93", "length");
      }

      if (length == 1)
      {
        return new long[] { 1 };
      }
      else if (length == 2)
      {
        return new long[] { 1, 1 };
      }
      else
      {
        f = new long[length];
        f[0] = 1;
        f[1] = 1;

        for (int i = 2; i < length; i++)
        {
          f[i] = f[i - 1] + f[i - 2];
        }

        return f;
      }
    }

    /// <summary>
    /// Get the stream of an embedded file.
    /// </summary>
    /// <param name="name">The file name.</param>
    /// <returns>The stream of the embedded file.</returns>
    private Stream GetResourceStream(string name)
    {
      Assembly asm;
      Stream s;

      asm = Assembly.GetExecutingAssembly();
      s = asm.GetManifestResourceStream("IntegrationTests.Samples.TestData.FibonacciSquared." + name);

      return s;
    }
  }
}
